//
// System.Runtime.Serialization.FormatterServices
//
// Authors:
//	Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (C) 2002 Ximian, Inc (http://www.ximian.com)
//

//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#ifndef FormatterServices_h
#define FormatterServices_h

#include <CSharpPropertyMethods.h>
#include <System/EventArgs.h>
#include <System/NotImplementedException.h>
#include <System/Runtime/Serialization/IFormatterConverter.h>
#include <System/Convert.h>
#include <System/eflection/BindingFlags.h>

namespace Riccsson
{
	namespace System
	{
		namespace Runtime
		{
			namespace Serialization
			{
				class FormatterConverter : public IFormatterConverter
				{
					class FormatterServices sealed
					{
						private: const Reflection::BindingFlags fieldFlags;

						private: FormatterServices ()
							: fieldFlags(Reflection::BindingFlags::Public |
										 Reflection::BindingFlags::Instance |
										 Reflection::BindingFlags::NonPublic |
										 Reflection::BindingFlags::DeclaredOnly)
						{
						}

						public: static GC_PTR<Array<GC_PTR<object>>> GetObjectData (object* obj, Array<GC_PTR<MemberInfo>>* members)
						{
							if (obj == null)
								throw ArgumentNullException ("obj");

							if (members == null)
								throw ArgumentNullException ("members");

							int n = members->Length;
							GC_PTR<Array<GC_PTR<object>>> result = new Array<GC_PTR<object>>(n);
							for (int i = 0; i < n; i++) {
								GC_PTR<MemberInfo> member = (*members)[i];
								if (member == null)
									throw ArgumentNullException (String::Format ("members[{0}]", i));

								if (member->MemberType != MemberTypes->Field)
									throw SerializationException (
											String::Format ("members [{0}] is not a field.", i));

								GC_PTR<FieldInfo> fi = (FieldInfo*)member; // members must be fields
								result [i] = fi.GetValue (obj);
							}

							return result;
						}

						public: static GC_PTR<Array<GC_PTR<MemberInfo>>> GetSerializableMembers (Type* type)
						{
							GC_PTR<StreamingContext> st = new StreamingContext (StreamingContextStates::All);
							return GetSerializableMembers (type, st);
						}

						public: static GC_PTR<Array<GC_PTR<MemberInfo>>> GetSerializableMembers (Type* type, StreamingContext* context)
						{
							if (type == null)
								throw ArgumentNullException ("type");

							//FIXME: context?
							Collections::ArrayList fields = new Collections::ArrayList ();
							Type* t = type;
							while (t != null) {
								if (!t->IsSerializable) {
									GC_PTR<string> msg = String::Format ("Type {0} in assembly {1} is not " +
													"marked as serializable.",
													t, t->Assembly.FullName);

									throw SerializationException (msg);
								}

								GetFields (type, t, fields);
								t = t.BaseType;
							}

							GC_PTR<Array<GC_PTR<MemberInfo>>> result = new Array<GC_PTR<MemberInfo>>(fields.Count);
							fields.CopyTo (result);
							return result;
						}

						private: static void GetFields (Type* reflectedType, Type* type, Collections::ArrayList* fields)
						{
							GC_PTR<Array<GC_PTR<MemberInfo>>> fs = type.GetFields (fieldFlags);
							foreach (FieldInfo field in fs)
								if (!(field.IsNotSerialized)) {
									MonoField mf = field as MonoField;
									if (mf != null && reflectedType != type && !mf.IsPublic) {
										string fname = type.Name + "+" + mf.Name;
										fields.Add (mf.Clone (fname));
									}
									else
										fields.Add (field);
								}
						}

						public: static GC_PTR<Type> GetTypeFromAssembly (Assembly* assem, string* name)
						{
							if (assem == null)
								throw ArgumentNullException ("assem");

							if (name == null)
								throw ArgumentNullException ("name");

							return assem->GetType (name);
						}

						public: static GC_PTR<object> GetUninitializedObject (Type* type)
						{
							if (type == null)
								throw ArgumentNullException ("type");

							if (type == typeof (string))
								throw ArgumentException ("Uninitialized Strings cannot be created.");

							return System::Runtime::Remoting::Activation::ActivationServices::AllocateUninitializedClassInstance (type);
						}

						public: static GC_PTR<object> PopulateObjectMembers (object* obj, Array<GC_PTR<MemberInfo>>* members, Array<GC_PTR<object>>* data)
						{
							if (obj == null)
								throw ArgumentNullException ("obj");

							if (members == null)
								throw ArgumentNullException ("members");

							if (data == null)
								throw ArgumentNullException ("data");

							int length = members.Length;
							if (length != data.Length)
								throw ArgumentException ("different length in members and data");

							for (int i = 0; i < length; i++) {
								MemberInfo member = members [i];
								if (member == null)
									throw ArgumentNullException (String.Format ("members[{0}]", i));
					
								if (member.MemberType != MemberTypes.Field)
									throw SerializationException (
											String.Format ("members [{0}] is not a field.", i));

								FieldInfo fi = member as FieldInfo; // members must be fields
								fi.SetValue (obj, data [i]);
							}

							return obj;
						}
		

						public: static void CheckTypeSecurity (Type* t, TypeFilterLevel securityLevel)
						{
							if (securityLevel == TypeFilterLevel::Full) return;
							CheckNotAssignable (typeof(System.DelegateSerializationHolder), t);
							CheckNotAssignable (typeof(System.Runtime.Remoting.Lifetime.ISponsor), t);
							CheckNotAssignable (typeof(System.Runtime.Remoting.IEnvoyInfo), t);
							CheckNotAssignable (typeof(System.Runtime.Remoting.ObjRef), t);
						}
		
						static void CheckNotAssignable (Type* basetype, Type* type)
						{
							if (basetype.IsAssignableFrom (type)) {
								string msg = "Type " + basetype + " and the types derived from it";
								msg += " (such as " + type + ") are not permitted to be deserialized at this security level";
								throw System.Security.SecurityException (msg);
							}
						}

						public: static GC_PTR<object> GetSafeUninitializedObject (Type* type)
						{
							// FIXME: MS.NET uses code access permissions to check if the caller is
							// allowed to create an instance of this type. We can't support this
							// because it is not implemented in mono.
			
							// In concrete, the it will request a SecurityPermission of 
							// type "Infrastructure".
			
							return GetUninitializedObject (type);
						}

				#if NET_4_0
						// This method was introduced in .Net due to a bug serializing objects with circular references
						// which we don't appear to have, so we just return the same object.
						// See http://support.microsoft.com/kb/927495/en-us/ in case of doubt.
						[ComVisible (false)]
						public: static ISerializationSurrogate GetSurrogateForCyclicalReference (ISerializationSurrogate innerSurrogate)
						{
							return innerSurrogate;
						}
				#endif
					};
				};
			}
		}
	}
}

#endif